﻿using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using Cpuss.Properties;
using NPlot;
using PlotSurface2D=NPlot.Bitmap.PlotSurface2D;

namespace Cpuss
{
    /// <summary>
    /// Creates a HTML report for a given <see cref="RepeatRunner"/>.
    /// </summary>
    public class HtmlReport
    {
        private readonly int[] _abscissa;
        private readonly string _fileName;
        private readonly string _outdir;
        private readonly RepeatRunner _repeatRunner;

        /// <summary>
        /// Initializes a new instance of <see cref="HtmlReport"/>.
        /// </summary>
        /// <remarks>
        /// The output directory specified must be the fully qualified directory name, e.g. <em>C:\parent\targetdir</em>.
        /// </remarks>
        /// <param name="repeatRunner"><see cref="RepeatRunner"/> to build the report for.</param>
        /// <param name="fileName">File name to use for the report</param>
        /// <param name="outdir">Output directory of the report.</param>
        /// <exception cref="ArgumentNullException">
        /// <para>
        /// <strong>repeatRunner</strong> is <strong>null</strong>.
        /// </para>
        /// <para>
        /// -- or --
        /// </para>
        /// <para>
        /// <strong>fileName</strong> is <strong>null</strong>.
        /// </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para>
        /// <strong>fileName</strong> has a length less than <strong>1</strong>.
        /// </para>
        /// <para>
        /// <strong>outdir</strong> has a length less than <strong>3</strong>.
        /// </para>
        /// </exception>
        /// <exception cref="DirectoryNotFoundException">
        /// <strong>outdir</strong> does not <strong>exist</strong>.
        /// </exception>
        public HtmlReport(RepeatRunner repeatRunner, string fileName, string outdir)
        {
            if (repeatRunner == null)
            {
                throw new ArgumentNullException("repeatRunner");
            }
            else if (fileName == null)
            {
                throw new ArgumentNullException("fileName", Resources.FileNameNotSpecified);
            }
            else if (fileName.Length < 1)
            {
                throw new ArgumentException(Resources.FileNameLessThanOneChar);
            }
            else if (outdir == null)
            {
                _outdir = Directory.GetCurrentDirectory();
            }
            else if (outdir.Length < 3)
            {
                throw new ArgumentException(Resources.OutputDirectoryLessThanThree);
            }
            else if (!Directory.Exists(outdir))
            {
                throw new DirectoryNotFoundException(Resources.OutputDirectoryNonExistant);
            }

            _repeatRunner = repeatRunner;
            _fileName = Path.GetFileNameWithoutExtension(fileName);
            if (_outdir == null)
            {
                _outdir = outdir;
            }
            _abscissa = new int[_repeatRunner.Repeat];
            for (int i = 0; i < _abscissa.Length; i++)
            {
                _abscissa[i] = i + 1;
            }
        }

        /// <summary>
        /// Gets the <see cref="RepeatRunner"/> the report is built on.
        /// </summary>
        public RepeatRunner RepeatRunner
        {
            get { return _repeatRunner; }
        }

        /// <summary>
        /// Gets the file name used for the report.
        /// </summary>
        public string FileName
        {
            get { return string.Format(CultureInfo.InvariantCulture, "{0}.html", _fileName); }
        }

        /// <summary>
        /// Gets the output directory the report file will be placed in.
        /// </summary>
        public string OutputDirectory
        {
            get { return _outdir; }
        }

        /// <summary>
        /// Creates the report.
        /// </summary>
        public void Create()
        {
            _repeatRunner.Run();
            using (
                StreamWriter streamWriter =
                    File.AppendText(string.Format(CultureInfo.InvariantCulture, "{0}\\{1}.html", _outdir, _fileName)))
            {
                GenerateHtmlHeader(streamWriter);
                GenerateBody(streamWriter);
                streamWriter.WriteLine("</body></html>");
            }
        }

        /// <summary>
        /// Generates the head section of the HTML report.
        /// </summary>
        /// <param name="streamWriter">Wrtier stream to use.</param>
        private void GenerateHtmlHeader(StreamWriter streamWriter)
        {
            streamWriter.WriteLine(
                "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
            streamWriter.WriteLine("<html xmlns=\"http://www.w3.org/1999/xhtml\" >");
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "<head><title>{0}</title>",
                                                 _repeatRunner.Strategy));
            streamWriter.WriteLine(
                "<style type=\"text/css\">body {font-family: Trebuchet MS;font-size: small;}h1 {font-weight: normal;}");
            streamWriter.WriteLine(
                "h2 {font-weight: normal;background-color: #CCCCCC;padding: 3px;}ul {list-style-type: none;margin: 0;}");
            streamWriter.WriteLine("table th {text-align: left;}</style>");
            streamWriter.WriteLine("</head><body>");
        }

        /// <summary>
        /// Generates the body of the report.
        /// </summary>
        /// <param name="streamWriter">Writer stream to use.</param>
        private void GenerateBody(StreamWriter streamWriter)
        {
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<h1>CPUSS Report</h1><p>Generated by CPUSS {0} on {1}.</p>",
                                                 Assembly.GetExecutingAssembly().GetName().Version, DateTime.Now));
            streamWriter.WriteLine(
                "<ul><li><a href=\"#overview\">Simulation Overview</a></li><li><a href=\"#avg\">Process Wait Times</a></li>");
            streamWriter.WriteLine("<li><a href=\"#cpu\">CPU Utilization</a></li><li><a href=\"#turnaround\">Process Turnaround/Response Times</a></li></ul>");

            // overview
            streamWriter.WriteLine("<h2 id=\"overview\">Simulation Overview</h2>");
            // table
            streamWriter.WriteLine(
                "<table width=\"725px\"><thead><tr><th style=\"width: 250px\">Property</th><th style=\"width: 250px\">Value</th></tr></thead>");
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "<tr><td>Strategy</td><td>{0}</td></tr>",
                                                 _repeatRunner.Strategy));
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "<tr><td># Runs</td><td>{0}</td></tr>",
                                                 _repeatRunner.Repeat));
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<tr><td># Processes</td><td>{0}</td></tr>", _repeatRunner.ProcessCount));
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<tr><td># Small Processes, Burst Time = ({0}ns - {1}ns)</td><td>{2}</td></tr>",
                                                 (int) BurstTime.SmallMin, (int) BurstTime.SmallMax, _repeatRunner.Small));
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<tr><td># Medium Processes, Burst Time = ({0}ns - {1}ns)</td><td>{2}</td></tr>",
                                                 (int) BurstTime.MediumMin, (int) BurstTime.MediumMax,
                                                 _repeatRunner.Medium));
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<tr><td># Large Processes, Burst Time = ({0}ns - {1}ns)</td><td>{2}</td></tr>",
                                                 (int) BurstTime.LargeMin, (int) BurstTime.LargeMax, _repeatRunner.Large));
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<tr><td>Throughput (Std Dev, Time Window = {0}ns)</td><td>{1}</td></tr>",
                                                 Math.Floor(_repeatRunner.GetThroughputStats().First),
                                                 Math.Round(_repeatRunner.GetThroughputStats().Second, 2)));
            streamWriter.WriteLine("</table>");

            // avg/std dev wait times
            streamWriter.WriteLine("<h2 id=\"avg\">Process Wait Times</h2>");
            // add avg/std dev graph
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<img src=\"{0}\" alt=\"Wait Times Standard Deviation\" />",
                                                 CreateWaitTimeGraph()));

            // cpu utilization
            streamWriter.WriteLine("<h2 id=\"cpu\">CPU Utilization</h2>");

            // create cpu utilization graph
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture,
                                                 "<img src=\"{0}\" alt=\"CPU Utilizations\" />",
                                                 CreateCpuUtilizationGraph()));

            // turnaround times
            streamWriter.WriteLine("<h2 id=\"turnaround\">Process Turnaround/Response Times</h2>");

            // create turnaround times graph
            streamWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "<img src=\"{0}\" alt=\"Process Turnaround/Response Times\" />",
                                                 CreateProcessResponseAndTurnaroundTimeGraph()));
        }

        /// <summary>
        /// Creates a graph for the process turnaround times.
        /// </summary>
        /// <returns>Path of graph image.</returns>
        private string CreateProcessResponseAndTurnaroundTimeGraph()
        {
            var surface = new PlotSurface2D(1000, 550) {Title = "Process Turnaround/Response Times", AutoScaleTitle = true};
            var legend = new Legend();
            legend.AttachTo(NPlot.PlotSurface2D.XAxisPosition.Top, NPlot.PlotSurface2D.YAxisPosition.Right);
            var turnaround = new LinePlot
                                 {
                                     DataSource = _repeatRunner.TurnaroundTimeStandardDeviations,
                                     AbscissaData = _abscissa,
                                     Color = Color.Blue,
                                     Label = "Turnaround Time (Std Dev)"
                                 };
            var response = new LinePlot()
                               {
                                   DataSource = _repeatRunner.ResponseTimeStandardDeviations,
                                   AbscissaData = _abscissa,
                                   Color = Color.Green,
                                   Label = "Response Time (Std Dev)"
                               };
            surface.Add(new Grid());
            surface.Add(turnaround);
            surface.Add(response);
            surface.XAxis1.Label = "Run";
            surface.YAxis1.Label = "Time (ns)";
            surface.Legend = legend;
            return SaveImage(surface,
                             string.Format(CultureInfo.InvariantCulture, "{0}_process.png", _repeatRunner.Id));
        }

        /// <summary>
        /// Creates a graph for the wait times - avg/std dev.
        /// </summary>
        /// <returns>Path of graph image.</returns>
        private string CreateWaitTimeGraph()
        {
            var surface = new PlotSurface2D(1000, 550)
                              {
                                  Title = "Wait Times",
                                  AutoScaleTitle = true
                              };
            var legend = new Legend();
            legend.AttachTo(NPlot.PlotSurface2D.XAxisPosition.Top, NPlot.PlotSurface2D.YAxisPosition.Right);
            // std dev surface
            var std = new LinePlot
                          {
                              DataSource = _repeatRunner.WaitTimeStandardDeviations,
                              AbscissaData = _abscissa,
                              Color = Color.Blue,
                              Label = "Wait Time (Std Dev)"
                          };
            surface.Add(new Grid());
            // add plots to surface
            surface.Add(std);
            surface.XAxis1.Label = "Run";
            surface.YAxis1.Label = "Time (ns)";
            surface.Legend = legend;
            return SaveImage(surface, string.Format(CultureInfo.InvariantCulture, "{0}_stdDevWaitTimes.png", _repeatRunner.Id));
        }

        /// <summary>
        /// Creates a graph for the CPU utilizations.
        /// </summary>
        /// <returns>Path of graph image.</returns>
        private string CreateCpuUtilizationGraph()
        {
            var surface = new PlotSurface2D(1000, 550) {Title = "CPU Utilization", AutoScaleTitle = true};
            var legend = new Legend();
            legend.AttachTo(NPlot.PlotSurface2D.XAxisPosition.Top, NPlot.PlotSurface2D.YAxisPosition.Right);
            var utilization = new LinePlot
                                  {
                                      DataSource = _repeatRunner.CpuUtilizations,
                                      AbscissaData = _abscissa,
                                      Color = Color.Green,
                                      Label = "CPU Utilization (%)"
                                  };
            surface.Add(new Grid());
            surface.Add(utilization);
            surface.XAxis1.Label = "Run";
            surface.YAxis1.Label = "%";
            surface.Legend = legend;
            return SaveImage(surface,
                             string.Format(CultureInfo.InvariantCulture, "{0}_utilization.png", _repeatRunner.Id));
        }

        /// <summary>
        /// Saves a graph to the filesystem.
        /// </summary>
        /// <param name="surface">Graph surface.</param>
        /// <param name="fileName">File name of image.</param>
        private string SaveImage(PlotSurface2D surface, string fileName)
        {
            string path = string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", _outdir, fileName);
            using (Image image = new Bitmap(1000, 550))
            using (Graphics graphic = Graphics.FromImage(image))
            {
                graphic.FillRectangle(new SolidBrush(Color.White), 0, 0, 1000, 550);
                surface.Draw(graphic, new Rectangle(0, 0, 1000, 550));
                image.Save(path);
            }
            return fileName;
        }
    }
}